#!/bin/bash

usage() {
[[ "$@" != "" ]] && echo -e "\n$@\n\nTry -h for help.\n" || \
cat <<EOF
Simplistic Silent ScreenCast (GIF)

Dependencies
 Either
 - maim and bc and imagemagick's convert for method 1
 or
 - ffmpeg for method 2

Optional dependencies
 - slop for interactive region selection
 - gifsicle for optimisation (only method 2)

Options
 -g str  pass geometry string instead of interactively selecting it

 -w int  wait int seconds before starting (default: $wait)

 -f int  set frames per second (default: $fps)

 -d int  set duration of recording in seconds instead of quitting interactively

 -o str  define output file (default: $output)

 -m 1|2  define method to use:
           1 take screenshots with maim and stitch them together to a looping
             GIF. Try to record the exact number of frames per second, but if
             the screenshoting takes too long, don't try to keep up.
             this method is more resource intensive but produces _much_ smaller
             files.
           2 ffmpeg for both screencast and GIF conversion. If gifsicle is
             installed, optimise. Even optimised, filesize is 5x - 20x larger
             than with method 1.

         currently defaults to method $method.

 -v      don't convert intermediate video to gif. only applies to method 2

 -h      this text
EOF
exit 1
}
red() {
  # bright red text
  tput setaf 9
  echo "$*"
  tput sgr0
}
#~ method1() {
  #~ echo "Taking a series of screenshots with maim..."
  #~ index=0
  #~ while :; do
    #~ # how long to take a screenshot...
    #~ T="$({ time maim -m 1 -g $geom $tmpdir/$(printf "%09d" $index).png >/dev/null 2>&1; } 2>&1; )"
    #~ T="${T/.}" # now it's in milliseconds
    #~ T="${T##+(0)}" # strip leading zeros - see shopt -s extglob
    #~ # ...subtract this from the delay (=fps).
    #~ # to avoid using bc (i hope it's much faster that way, keeping the end result pure)
    #~ # we simply remove the dot '.', do our calculation, then put it back.
    #~ # this works because both time and bc have been told to use a precision of 3
    #~ # digits after the dot.
    #~ T="$(( delay - T ))" 
    #~ [[ "${T:0:1}" == "-" ]] && continue # nothing to do if result is negative
    #~ ((index++))
    #~ (( duration == index / fps )) && break
    #~ sleep ".$(printf "%03d" $T)"
  #~ done & pid=$!

  #~ if ((duration == 0)); then
	  #~ REPLY=''
	  #~ while [[ "$REPLY" != "q" ]]; do read -n1; sleep 0.2; done
	  #~ kill $pid >/dev/null 2>&1
	  #~ echo "'q' pressed, finishing up..."
  #~ else
	  #~ wait $pid
  #~ fi
  #~ echo "Converting screenshots to GIF with imagemagick's convert..."
  #~ convert -layers OptimizePlus -delay $(( delay / 10 )) $tmpdir/*.png -loop 0 "$output"
#~ }
method1() {
  echo "Taking a series of screenshots with maim..."
  index=0
  sleepdelay="$(bc<<<"scale=4;1/$fps")"
  while :; do
    coproc maim -m 1 -g $geom $tmpdir/$(printf "%09d" $index).png
    sleep $sleepdelay
    wait $COPROC_PID # see man bash - coprocesses, wait builtin
    # it should be done by now! if it really has to wait it means the system is congested and the fps needs to be lowered.
    #~ echo wait returned $!
    ((index++))
    (( duration == index / fps )) && break
  done >&2 & pid=$!

  if ((duration == -1)); then
	  red "Press 'q' to finish "
	  while [[ "$X" != "q" ]]; do read -sn1 X; sleep 0.2; done
	  kill $pid >/dev/null 2>&1
	  echo "'q' pressed, finishing up..."
  else
	  wait $pid
  fi
  echo "Converting screenshots to GIF with imagemagick's convert..."
  convert -layers OptimizePlus -delay $(( delay / 10 )) $tmpdir/*.png -loop 0 "$output"
}
method2() {
  palette="$tmpdir/palette.png"
  [[ "$video" == 1 ]] && output="sssc$now.mkv" || output="$tmpdir/video.mkv"
  time=''
  ((duration > 0)) && time="-t $duration"
  echo "Recording .mkv video with ffmpeg..."
  red "Press 'q' to finish "
  wh="${geom%%+*}"
  xy="${geom#*+}"
  xy="${xy/+/,}"
  ffmpeg -hide_banner -f x11grab -framerate "$fps" -video_size ${wh} $time -i :0.0+${xy} -c:v libx264rgb -preset ultrafast -y "$output"
  if [[ "$video" == 0 ]]; then
    echo "Converting video to GIF with ffmpeg..."
    filters="fps=$fps"
    # build palette
    ffmpeg -hide_banner -v warning -i "$output" -framerate "$fps" -vf "$filters,palettegen" -y "$palette"
    # convert to gif
    ffmpeg -hide_banner -v warning -i "$output" -i "$palette" -framerate "$fps" -lavfi "$filters [x]; [x][1:v] paletteuse=dither=none" "sssc$now.gif"
    # optimize
    which gifsicle >/dev/null 2>&1 && gifsicle -V -O3 --delay $(( delay / 10 )) "sssc$now.gif" -o "sssc${now}_optimized.gif"
  fi
}

# sleep as a builtin
for file in /usr/lib*/bash/sleep; do
    [ -r "$file" ] && enable -f "$file" sleep && break
done
# Portable enough?

printf -v now "%(%Y%m%d%H%M%S)T"
tmpdir="/tmp/sssc$now"
mkdir -p "$tmpdir"
trap "rm -r $tmpdir" EXIT

output=sssc$now.gif
geom=''
fps='10'
wait=2
duration=-1
TIMEFORMAT='%3R' # see man bash /TIMEFORMAT - needed for 'time'
min=2
max=50
method=1
video=0

while getopts "g:w:f:d:o:m:vh" opt; do
  case $opt in
    g) geom="$OPTARG"
       [[ "$geom" != [0-9]*x[0-9]*\+[0-9]*\+[0-9]* ]] && usage "$geom is not a valid geometry string"
      ;;
    w) (( OPTARG > 0 )) || usage "wait is $OPTARG, but must be larger than zero"
       wait="$OPTARG"
      ;;
    f) (( OPTARG >= $min )) && (( OPTARG <= $max )) || usage "fps is $OPTARG, but must be between $min and $max"
       fps="$OPTARG"
      ;;
    d) (( OPTARG > 0 )) || usage "duration is $OPTARG, but must be larger than zero"
       duration="$OPTARG"
      ;;
    o) output="${OPTARG##*.}"
       output="${output,,}"
       [[ "$output" != "gif" ]] && usage "\"$OPTARG\" - invalid extension. Must be .gif or .GIF"
       output="$OPTARG"
      ;;
    m) case "$OPTARG" in
	'2') method=2
	  ;;
	'1') which maim >/dev/null 2>&1 || usage "Can't find 'maim' in PATH. Maybe try '-m 2'?"
	  ;;
	*) usage "method is $method, but must be either 1 or 2"
	  ;;
	esac
      ;;
    v) video=1
      ;;
    h) usage
      ;;
  esac
done

[[ "$geom" == "" ]] && which slop >/dev/null 2>&1 || usage "No interactive selection without 'slop'"
#~ shopt -s extglob # needed for Time calculations - all in an effort to avoid bc

echo "Simplistic Silent Screencasting (GIF)
====================================="
while [[ "$geom" == "" ]]; do
	printf "\nPlease select a region. "
	geom="$(slop 2>/dev/null)"
done

printf "\nRecording region $geom @ ${fps}frames/s\nstarting in $wait seconds. "
sleep $wait
if ((duration > 0)); then
	printf "\nRecording now for $duration seconds."
else
	printf "\nPress 'q' to stop recording"
fi
printf "\n"

delay="$(( 1000 / $fps ))" # now it's in milliseconds

method$method
